package io.github.talelin.latticy.common.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.github.talelin.autoconfigure.exception.TokenInvalidException;
import io.github.talelin.core.constant.TokenConstant;
import io.github.talelin.core.token.Tokens;
import io.github.talelin.core.util.DateUtil;
import io.github.talelin.latticy.common.LocalUser;
import io.github.talelin.latticy.common.cache.RedisKeyPrefix;
import io.github.talelin.latticy.common.cache.RedisTime;
import io.github.talelin.latticy.common.cache.RedisUtil;
import io.github.talelin.latticy.common.pojo.IdentityConfigEntity;
import io.github.talelin.latticy.util.JedisUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.Date;
import java.util.Map;
import java.util.Optional;

/**
 * @ClassName：JwtService
 * @Author: ljj
 * @Date: 2022/5/31 10:23
 */
@Data
public class JwtService {

    @Autowired
    private IdentityConfigEntity identityList;

    private long accessExpire;
    private long refreshExpire;
    private Algorithm algorithm;
    private JWTVerifier accessVerifier;
    private JWTVerifier refreshVerifier;
    private JWTCreator.Builder builder;

    public JwtService(String secret, long accessExpire, long refreshExpire) {
        this.algorithm = Algorithm.HMAC256(secret);
        this.accessExpire = accessExpire;
        this.refreshExpire = refreshExpire;
        this.initBuilderAndVerifier();
    }

    public String createOmsToken() {
        Optional.ofNullable(LocalUser.getLocalUser()).orElseThrow(() -> new TokenInvalidException(10041));
        return generateToken("access", LocalUser.getLocalUser().getId(), "oms", accessExpire);
    }

    public String generateToken(String tokenType, long identity, String scope, long expire) {
        Date expireDate = DateUtil.getDurationDate(expire);
        return this.builder.withIssuer(identityList.getList().get(0)).withClaim("type", tokenType).withClaim("identity", identity).withClaim("scope", scope).withExpiresAt(expireDate).sign(this.algorithm);
    }

    public DecodedJWT decodeToken(String token) {
        DecodedJWT jwt = this.accessVerifier.verify(token);
        checkTokenExpired(jwt.getExpiresAt());
        checkTokenType(jwt.getClaim("type").asString(), TokenConstant.ACCESS_TYPE);
        return jwt;
    }

    public Tokens generateTokens(long identity) {
        String access = this.generateToken("access", identity, "oms", this.accessExpire);
        String refresh = this.generateToken("refresh", identity, "oms", this.refreshExpire);
        long l = this.accessExpire - RedisTime.FIVE_MIUNTES;
        return new Tokens(access, refresh);
    }

    public Map<String, Claim> decodeRefreshToken(String token) {
        DecodedJWT jwt = refreshVerifier.verify(token);
        checkTokenExpired(jwt.getExpiresAt());
        checkTokenType(jwt.getClaim("type").asString(), TokenConstant.REFRESH_TYPE);
        return jwt.getClaims();
    }

    private void initBuilderAndVerifier() {
        this.accessVerifier = JWT.require(this.algorithm).acceptExpiresAt(this.accessExpire).build();
        this.refreshVerifier = JWT.require(this.algorithm).acceptExpiresAt(this.refreshExpire).build();
        this.builder = JWT.create();
    }

    private void checkTokenType(String type, String accessType) {
        if (type == null || !type.equals(accessType)) {
            throw new InvalidClaimException("token type is invalid");
        }
    }

    private void checkTokenExpired(Date expiresAt) {
        long now = System.currentTimeMillis();
        if (expiresAt.getTime() < now) {
            throw new TokenExpiredException("token is expired");
        }
    }
}
